﻿using Grand.Business.Core.Interfaces.Catalog.Products;
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Business.Core.Interfaces.Common.Addresses;
using Grand.Business.Core.Interfaces.Common.Directory;
using Grand.Business.Core.Interfaces.Common.Localization;
using Grand.Business.Core.Interfaces.Common.Security;
using Grand.Business.Core.Interfaces.Customers;
using Grand.Business.Core.Interfaces.ExportImport;
using Grand.Business.Core.Interfaces.Messages;
using Grand.Domain.Permissions;
using Grand.Business.Core.Utilities.Customers;
using Grand.Domain.Catalog;
using Grand.Domain.Common;
using Grand.Domain.Customers;
using Grand.Domain.Tax;
using Grand.Infrastructure;
using Grand.SharedKernel;
using Grand.SharedKernel.Extensions;
using Grand.Web.Admin.Extensions;
using Grand.Web.Admin.Interfaces;
using Grand.Web.Admin.Models.Catalog;
using Grand.Web.Admin.Models.Customers;
using Grand.Web.Admin.Models.Orders;
using Grand.Web.Common.DataSource;
using Grand.Web.Common.Filters;
using Grand.Web.Common.Models;
using Grand.Web.Common.Security.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Grand.Web.Admin.Controllers;

[PermissionAuthorize(PermissionSystemName.Customers)]
public class CustomerController : BaseAdminController
{
    #region Constructors

    public CustomerController(ICustomerService customerService,
        IProductReviewService productReviewService,
        IProductReviewViewModelService productReviewViewModelService,
        IProductViewModelService productViewModelService,
        ICustomerViewModelService customerViewModelService,
        ICustomerManagerService customerManagerService,
        ITranslationService translationService,
        IWorkContextAccessor workContextAccessor,
        IGroupService groupService,
        IExportManager<Customer> exportManager,
        ICustomerAttributeParser customerAttributeParser,
        ICustomerAttributeService customerAttributeService,
        IAddressAttributeParser addressAttributeParser,
        IAddressAttributeService addressAttributeService,
        IMessageProviderService messageProviderService,
        IPermissionService permissionService,
        CustomerSettings customerSettings)
    {
        _customerService = customerService;
        _productReviewService = productReviewService;
        _productReviewViewModelService = productReviewViewModelService;
        _productViewModelService = productViewModelService;
        _customerViewModelService = customerViewModelService;
        _customerManagerService = customerManagerService;
        _translationService = translationService;
        _workContextAccessor = workContextAccessor;
        _groupService = groupService;
        _exportManager = exportManager;
        _customerAttributeParser = customerAttributeParser;
        _customerAttributeService = customerAttributeService;
        _addressAttributeParser = addressAttributeParser;
        _addressAttributeService = addressAttributeService;
        _messageProviderService = messageProviderService;
        _permissionService = permissionService;
        _customerSettings = customerSettings;
    }

    #endregion

    protected virtual async Task<IList<CustomAttribute>> ParseCustomCustomerAttributes(
        IList<CustomAttributeModel> model)
    {
        ArgumentNullException.ThrowIfNull(model);

        var customAttributes = new List<CustomAttribute>();
        var customerAttributes = await _customerAttributeService.GetAllCustomerAttributes();
        foreach (var attribute in customerAttributes)
        {
            var controlId = $"customer_attribute_{attribute.Id}";
            switch (attribute.AttributeControlTypeId)
            {
                case AttributeControlType.DropdownList:
                case AttributeControlType.RadioList:
                {
                    var ctrlAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
                    if (!string.IsNullOrEmpty(ctrlAttributes))
                        customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
                            attribute, ctrlAttributes).ToList();
                }
                    break;
                case AttributeControlType.Checkboxes:
                {
                    var cblAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
                    if (!string.IsNullOrEmpty(cblAttributes))
                        foreach (var item in cblAttributes.Split(','))
                            if (!string.IsNullOrEmpty(item))
                                customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
                                    attribute, item).ToList();
                }
                    break;
                case AttributeControlType.ReadonlyCheckboxes:
                {
                    //load read-only (already server-side selected) values
                    var attributeValues = attribute.CustomerAttributeValues;
                    foreach (var selectedAttributeId in attributeValues
                                 .Where(v => v.IsPreSelected)
                                 .Select(v => v.Id)
                                 .ToList())
                        customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
                            attribute, selectedAttributeId).ToList();
                }
                    break;
                case AttributeControlType.TextBox:
                case AttributeControlType.MultilineTextbox:
                {
                    var ctrlAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
                    if (!string.IsNullOrEmpty(ctrlAttributes))
                    {
                        var enteredText = ctrlAttributes.Trim();
                        customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
                            attribute, enteredText).ToList();
                    }
                }
                    break;
                case AttributeControlType.Datepicker:
                case AttributeControlType.ColorSquares:
                case AttributeControlType.ImageSquares:
                case AttributeControlType.FileUpload:
                //not supported customer attributes
                default:
                    break;
            }
        }

        return customAttributes;
    }

    protected virtual async Task<bool> CheckSalesManager(Customer customer)
    {
        return await _groupService.IsSalesManager(_workContextAccessor.WorkContext.CurrentCustomer)
               && _workContextAccessor.WorkContext.CurrentCustomer.SeId != customer.SeId;
    }

    #region Message contact form

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> ContactFormList(DataSourceRequest command, string customerId)
    {
        var (contactFormModels, totalCount) =
            await _customerViewModelService.PrepareContactFormModel(customerId, command.Page, command.PageSize);
        var gridModel = new DataSourceResult {
            Data = contactFormModels.ToList(),
            Total = totalCount
        };
        return Json(gridModel);
    }

    #endregion

    #region Out of stock subscriptions

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> OutOfStockSubscriptionList(DataSourceRequest command, string customerId)
    {
        var (outOfStockSubscriptionModels, totalCount) =
            await _customerViewModelService.PrepareOutOfStockSubscriptionModel(customerId, command.Page,
                command.PageSize);
        var gridModel = new DataSourceResult {
            Data = outOfStockSubscriptionModels.ToList(),
            Total = totalCount
        };
        return Json(gridModel);
    }

    #endregion

    #region Fields

    private readonly ICustomerService _customerService;
    private readonly IGroupService _groupService;
    private readonly IProductReviewService _productReviewService;
    private readonly IProductReviewViewModelService _productReviewViewModelService;
    private readonly IProductViewModelService _productViewModelService;
    private readonly ICustomerViewModelService _customerViewModelService;
    private readonly ICustomerManagerService _customerManagerService;
    private readonly ITranslationService _translationService;
    private readonly IWorkContextAccessor _workContextAccessor;
    private readonly IExportManager<Customer> _exportManager;
    private readonly ICustomerAttributeParser _customerAttributeParser;
    private readonly ICustomerAttributeService _customerAttributeService;
    private readonly IAddressAttributeParser _addressAttributeParser;
    private readonly IAddressAttributeService _addressAttributeService;
    private readonly IMessageProviderService _messageProviderService;
    private readonly IPermissionService _permissionService;
    private readonly CustomerSettings _customerSettings;

    #endregion

    #region Customers

    public IActionResult Index()
    {
        return RedirectToAction("List");
    }

    public async Task<IActionResult> List()
    {
        var model = await _customerViewModelService.PrepareCustomerListModel();
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.List)]
    [HttpPost]
    public async Task<IActionResult> CustomerList(DataSourceRequest command, CustomerListModel model,
        string[] searchCustomerGroupIds, string[] searchCustomerTagIds)
    {
        var (customerModelList, totalCount) = await _customerViewModelService.PrepareCustomerList(model,
            searchCustomerGroupIds, searchCustomerTagIds, command.Page, command.PageSize);
        var gridModel = new DataSourceResult {
            Data = customerModelList.ToList(),
            Total = totalCount
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Create)]
    public async Task<IActionResult> Create()
    {
        var model = new CustomerModel();
        await _customerViewModelService.PrepareCustomerModel(model, null, false);
        //default value
        model.Active = true;
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")]
    public async Task<IActionResult> Create(CustomerModel model, bool continueEditing)
    {
        if (model.TwoFactorEnabled)
            Warning(_translationService.GetResource("Admin.Customers.Customers.CannotTwoFactorEnabled"));

        if (ModelState.IsValid)
        {
            model.Attributes = await ParseCustomCustomerAttributes(model.SelectedAttributes);
            var customer = await _customerViewModelService.InsertCustomerModel(model);

            //password
            if (!string.IsNullOrWhiteSpace(model.Password))
            {
                var changePassRequest = new ChangePasswordRequest(model.Email, _customerSettings.DefaultPasswordFormat,
                    model.Password);
                await _customerManagerService.ChangePassword(changePassRequest);
            }

            Success(_translationService.GetResource("Admin.Customers.Customers.Added"));
            return continueEditing ? RedirectToAction("Edit", new { id = customer.Id }) : RedirectToAction("List");
        }

        //If we got this far, something failed, redisplay form
        await _customerViewModelService.PrepareCustomerModel(model, null, true);
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    public async Task<IActionResult> Edit(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        var model = new CustomerModel();
        await _customerViewModelService.PrepareCustomerModel(model, customer, false);
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")]
    public async Task<IActionResult> Edit(CustomerModel model, bool continueEditing)
    {
        var customer = await _customerService.GetCustomerById(model.Id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        if (!customer.GetUserFieldFromEntity<bool>(SystemCustomerFieldNames.TwoFactorEnabled) && model.TwoFactorEnabled)
            Warning(_translationService.GetResource("Admin.Customers.Customers.CannotTwoFactorEnabled"));

        if (ModelState.IsValid)
            try
            {
                model.Attributes = await ParseCustomCustomerAttributes(model.SelectedAttributes);
                customer = await _customerViewModelService.UpdateCustomerModel(customer, model);
                //change password
                if (!string.IsNullOrWhiteSpace(model.Password))
                {
                    var changePassRequest = new ChangePasswordRequest(model.Email,
                        _customerSettings.DefaultPasswordFormat, model.Password);
                    await _customerManagerService.ChangePassword(changePassRequest);
                }

                Success(_translationService.GetResource("Admin.Customers.Customers.Updated"));
                if (continueEditing)
                {
                    //selected tab
                    await SaveSelectedTabIndex();

                    return RedirectToAction("Edit", new { id = customer.Id });
                }

                return RedirectToAction("List");
            }
            catch (Exception exc)
            {
                Error(exc.Message);
            }

        //If we got this far, something failed, redisplay form
        await _customerViewModelService.PrepareCustomerModel(model, customer, true);
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> MarkVatNumberAsValid(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        await _customerService.UpdateUserField(customer,
            SystemCustomerFieldNames.VatNumberStatusId,
            (int)VatNumberStatus.Valid);

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> MarkVatNumberAsInvalid(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        await _customerService.UpdateUserField(customer,
            SystemCustomerFieldNames.VatNumberStatusId,
            (int)VatNumberStatus.Invalid);

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> RemoveAffiliate(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        customer.AffiliateId = "";
        await _customerService.UpdateCustomerField(customer.Id, x => x.AffiliateId, customer.AffiliateId);
        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [PermissionAuthorizeAction(PermissionActionName.Delete)]
    [HttpPost]
    public async Task<IActionResult> Delete(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        if (customer.Id == _workContextAccessor.WorkContext.CurrentCustomer.Id)
        {
            Error(_translationService.GetResource("Admin.Customers.Customers.NoSelfDelete"));
            return RedirectToAction("List");
        }

        try
        {
            if (ModelState.IsValid)
            {
                await _customerViewModelService.DeleteCustomer(customer);
                Success(_translationService.GetResource("Admin.Customers.Customers.Deleted"));
                return RedirectToAction("List");
            }

            Error(ModelState);
            return RedirectToAction("Edit", new { id = customer.Id });
        }
        catch (Exception exc)
        {
            Error(exc.Message);
            return RedirectToAction("Edit", new { id = customer.Id });
        }
    }

    [PermissionAuthorizeAction(PermissionActionName.Delete)]
    public async Task<IActionResult> DeleteSelected(ICollection<string> selectedIds)
    {
        if (selectedIds != null) await _customerViewModelService.DeleteSelected(selectedIds.ToList());

        return Json(new { Result = true });
    }

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> Impersonate(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        if (!await _permissionService.Authorize(StandardPermission.AllowCustomerImpersonation))
        {
            Error("User does not have permission for the impersonate session");
            return RedirectToAction("Edit", customer.Id);
        }

        if (!await _groupService.IsAdmin(_workContextAccessor.WorkContext.CurrentCustomer) && await _groupService.IsAdmin(customer))
        {
            Error("A non-admin user cannot impersonate as an administrator");
            return RedirectToAction("Edit", customer.Id);
        }

        await _customerService.UpdateUserField(_workContextAccessor.WorkContext.CurrentCustomer,
            SystemCustomerFieldNames.ImpersonatedCustomerId, customer.Id);

        return RedirectToAction("Index", "Home", new { area = "" });
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> SendWelcomeMessage(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        await _messageProviderService.SendCustomerWelcomeMessage(customer, _workContextAccessor.WorkContext.CurrentStore,
            _workContextAccessor.WorkContext.WorkingLanguage.Id);

        Success(_translationService.GetResource("Admin.Customers.Customers.SendWelcomeMessage.Success"));

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> ReSendActivationMessage(string id)
    {
        var customer = await _customerService.GetCustomerById(id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        //email validation message
        await _customerService.UpdateUserField(customer, SystemCustomerFieldNames.AccountActivationToken,
            Guid.NewGuid().ToString());
        await _messageProviderService.SendCustomerEmailValidationMessage(customer, _workContextAccessor.WorkContext.CurrentStore,
            _workContextAccessor.WorkContext.WorkingLanguage.Id);

        Success(_translationService.GetResource("Admin.Customers.Customers.ReSendActivationMessage.Success"));

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> SendEmail(CustomerModel.SendEmailModel model)
    {
        var customer = await _customerService.GetCustomerById(model.Id);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        try
        {
            if (string.IsNullOrWhiteSpace(customer.Email))
                throw new GrandException("Customer email is empty");
            if (!CommonHelper.IsValidEmail(customer.Email))
                throw new GrandException("Customer email is not valid");
            if (string.IsNullOrWhiteSpace(model.Subject))
                throw new GrandException("Email subject is empty");
            if (string.IsNullOrWhiteSpace(model.Body))
                throw new GrandException("Email body is empty");

            await _customerViewModelService.SendEmail(customer, model);

            Success(_translationService.GetResource("Admin.Customers.Customers.SendEmail.Queued"));
        }
        catch (Exception exc)
        {
            Error(exc.Message);
        }

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    #endregion

    #region Loyalty points history

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> LoyaltyPointsHistorySelect(string customerId)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id");

        var model = (await _customerViewModelService.PrepareLoyaltyPointsHistoryModel(customerId)).ToList();
        var gridModel = new DataSourceResult {
            Data = model,
            Total = model.Count
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> LoyaltyPointsHistoryAdd(string customerId, string storeId,
        int addLoyaltyPointsValue, string addLoyaltyPointsMessage)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            return Json(new { Result = false });

        await _customerViewModelService.InsertLoyaltyPointsHistory(customer, storeId, addLoyaltyPointsValue,
            addLoyaltyPointsMessage);

        return Json(new { Result = true });
    }

    #endregion

    #region Addresses

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> AddressesSelect(string customerId, DataSourceRequest command)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id", nameof(customerId));

        var addresses = (await _customerViewModelService.PrepareAddressModel(customer)).ToList();
        var gridModel = new DataSourceResult {
            Data = addresses,
            Total = addresses.Count
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> AddressDelete(string id, string customerId)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id", nameof(customerId));

        var address = customer.Addresses.FirstOrDefault(a => a.Id == id);
        if (address == null)
            //No customer found with the specified id
            return Content("No customer found with the specified id");
        if (ModelState.IsValid)
        {
            await _customerViewModelService.DeleteAddress(customer, address);
            return new JsonResult("");
        }

        return ErrorForKendoGridJson(ModelState);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> AddressCreate(string customerId)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        var model = new CustomerAddressModel();
        await _customerViewModelService.PrepareAddressModel(model, null, customer, false);

        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> AddressCreate(CustomerAddressModel model)
    {
        var customer = await _customerService.GetCustomerById(model.CustomerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        if (ModelState.IsValid)
        {
            //custom address attributes
            var customAttributes =
                await model.Address.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService);
            var address = await _customerViewModelService.InsertAddressModel(customer, model, customAttributes);
            Success(_translationService.GetResource("Admin.Customers.Customers.Addresses.Added"));
            return RedirectToAction("AddressEdit", new { addressId = address.Id, customerId = model.CustomerId });
        }

        //If we got this far, something failed, redisplay form
        await _customerViewModelService.PrepareAddressModel(model, null, customer, true);
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> AddressEdit(string addressId, string customerId)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        var address = customer.Addresses.FirstOrDefault(x => x.Id == addressId);
        if (address == null)
            //No address found with the specified id
            return RedirectToAction("Edit", new { id = customer.Id });

        var model = new CustomerAddressModel();
        await _customerViewModelService.PrepareAddressModel(model, address, customer, false);
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> AddressEdit(CustomerAddressModel model)
    {
        var customer = await _customerService.GetCustomerById(model.CustomerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            //No customer found with the specified id
            return RedirectToAction("List");

        var address = customer.Addresses.FirstOrDefault(x => x.Id == model.Address.Id);
        if (address == null)
            //No address found with the specified id
            return RedirectToAction("Edit", new { id = customer.Id });

        if (ModelState.IsValid)
        {
            //custom address attributes
            var customAttributes =
                await model.Address.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService);
            address = await _customerViewModelService.UpdateAddressModel(customer, address, model, customAttributes);
            Success(_translationService.GetResource("Admin.Customers.Customers.Addresses.Updated"));
            return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, customerId = model.CustomerId });
        }

        //If we got this far, something failed, redisplay form
        await _customerViewModelService.PrepareAddressModel(model, address, customer, true);

        return View(model);
    }

    #endregion

    #region Orders

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> OrderList(string customerId, DataSourceRequest command,
        [FromServices] IOrderViewModelService orderViewModelService)
    {
        if (!await _permissionService.Authorize(StandardPermission.ManageOrders))
            return Json(new DataSourceResult {
                Data = null,
                Total = 0
            });

        var model = new OrderListModel {
            CustomerId = customerId
        };
        if (await _groupService.IsStaff(_workContextAccessor.WorkContext.CurrentCustomer))
            model.StoreId = _workContextAccessor.WorkContext.CurrentCustomer.StaffStoreId;

        var (orderModels, totalCount) =
            await orderViewModelService.PrepareOrderModel(model, command.Page, command.PageSize);
        var gridModel = new DataSourceResult {
            Data = orderModels.ToList(),
            Total = totalCount
        };
        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> OrderDetails(string orderId,
        [FromServices] IOrderService orderService, [FromServices] IOrderViewModelService orderViewModelService)
    {
        if (!await _permissionService.Authorize(StandardPermission.ManageOrders))
            return Json(new DataSourceResult {
                Data = null,
                Total = 0
            });

        var order = await orderService.GetOrderById(orderId);
        if (order == null)
            throw new ArgumentException("No order found with the specified id");

        if (await _groupService.IsStaff(_workContextAccessor.WorkContext.CurrentCustomer) &&
            order.StoreId != _workContextAccessor.WorkContext.CurrentCustomer.StaffStoreId)
            return Json(new DataSourceResult {
                Data = null,
                Total = 0
            });

        var ordermodel = new OrderModel();
        await orderViewModelService.PrepareOrderDetailsModel(ordermodel, order);
        var gridModel = new DataSourceResult {
            Data = ordermodel.Items,
            Total = ordermodel.Items.Count
        };

        return Json(gridModel);
    }

    #endregion

    #region Reviews

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> ReviewList(string customerId, DataSourceRequest command)
    {
        var productReviews = await _productReviewService.GetAllProductReviews(customerId, null,
            null, null, "", null, "", command.Page - 1, command.PageSize);
        var items = new List<ProductReviewModel>();
        foreach (var x in productReviews)
        {
            var m = new ProductReviewModel();
            await _productViewModelService.PrepareProductReviewModel(m, x, false, true);
            items.Add(m);
        }

        var gridModel = new DataSourceResult {
            Data = items,
            Total = productReviews.TotalCount
        };
        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> ReviewDelete(string id)
    {
        var productReview = await _productReviewService.GetProductReviewById(id);
        if (productReview == null)
            throw new ArgumentException("No review found with the specified id", nameof(id));

        await _productReviewViewModelService.DeleteProductReview(productReview);
        return new JsonResult("");
    }

    #endregion

    #region Current shopping cart/ wishlist

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> GetCartList(string customerId, int cartTypeId)
    {
        var cart = await _customerViewModelService.PrepareShoppingCartItemModel(customerId, cartTypeId);
        var gridModel = new DataSourceResult {
            Data = cart,
            Total = cart.Count
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> UpdateCart(string id, string customerId, double? unitPriceValue)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id", nameof(customerId));

        var warnings = await _customerViewModelService.UpdateCart(customer, id, unitPriceValue);
        if (warnings.Any())
            return ErrorForKendoGridJson(string.Join(",", warnings));

        return new JsonResult("");
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> DeleteCart(string id, string customerId)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id", nameof(customerId));

        await _customerViewModelService.DeleteCart(customer, id);

        return new JsonResult("");
    }

    #endregion

    #region Customer Product Personalize / Price

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> ProductsPrice(DataSourceRequest command, string customerId)
    {
        var (productPriceModels, totalCount) =
            await _customerViewModelService.PrepareProductPriceModel(customerId, command.Page, command.PageSize);
        var gridModel = new DataSourceResult {
            Data = productPriceModels.ToList(),
            Total = totalCount
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> PersonalizedProducts(DataSourceRequest command, string customerId)
    {
        var (productModels, totalCount) =
            await _customerViewModelService.PreparePersonalizedProducts(customerId, command.Page, command.PageSize);
        var gridModel = new DataSourceResult {
            Data = productModels.ToList(),
            Total = totalCount
        };
        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> ProductAddPopup(string customerId)
    {
        var model = await _customerViewModelService.PrepareCustomerModelAddProductModel();
        return View(model);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> ProductAddPopupList(DataSourceRequest command, CustomerModel.AddProductModel model)
    {
        var products = await _customerViewModelService.PrepareProductModel(model, command.Page, command.PageSize);

        var gridModel = new DataSourceResult {
            Data = products.products.ToList(),
            Total = products.totalCount
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> ProductAddPopup(string customerId, bool personalized,
        CustomerModel.AddProductModel model)
    {
        if (model.SelectedProductIds != null)
            await _customerViewModelService.InsertCustomerAddProductModel(customerId, personalized, model);
        return Content("");
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> UpdateProductPrice(CustomerModel.ProductPriceModel model)
    {
        await _customerViewModelService.UpdateProductPrice(model);
        return new JsonResult("");
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> DeleteProductPrice(string id)
    {
        await _customerViewModelService.DeleteProductPrice(id);
        return new JsonResult("");
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> UpdatePersonalizedProduct(CustomerModel.ProductModel model)
    {
        await _customerViewModelService.UpdatePersonalizedProduct(model);
        return new JsonResult("");
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> DeletePersonalizedProduct(string id)
    {
        await _customerViewModelService.DeletePersonalizedProduct(id);
        return new JsonResult("");
    }

    #endregion

    #region Customer note

    [PermissionAuthorizeAction(PermissionActionName.Preview)]
    [HttpPost]
    public async Task<IActionResult> CustomerNotesSelect(string customerId, DataSourceRequest command)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id");

        var customerNoteModels = await _customerViewModelService.PrepareCustomerNoteList(customerId);
        var gridModel = new DataSourceResult {
            Data = customerNoteModels,
            Total = customerNoteModels.Count
        };

        return Json(gridModel);
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    public async Task<IActionResult> CustomerNoteAdd(string customerId, string downloadId, bool displayToCustomer,
        string title, string message)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            return Json(new { Result = false });

        await _customerViewModelService.InsertCustomerNote(customerId, downloadId, displayToCustomer, title, message);

        return Json(new { Result = true });
    }

    [PermissionAuthorizeAction(PermissionActionName.Edit)]
    [HttpPost]
    public async Task<IActionResult> CustomerNoteDelete(string id, string customerId)
    {
        var customer = await _customerService.GetCustomerById(customerId);
        if (customer == null || customer.Deleted || await CheckSalesManager(customer))
            throw new ArgumentException("No customer found with the specified id");

        await _customerViewModelService.DeleteCustomerNote(id, customerId);

        return new JsonResult("");
    }

    #endregion

    #region Export

    [PermissionAuthorizeAction(PermissionActionName.Export)]
    [HttpPost]
    public async Task<IActionResult> ExportExcelAll(CustomerListModel model)
    {
        var salesEmployeeId =
            await _groupService.IsSalesManager(_workContextAccessor.WorkContext.CurrentCustomer) ? _workContextAccessor.WorkContext.CurrentCustomer.SeId : "";

        var customers = await _customerService.GetAllCustomers(
            customerGroupIds: model.SearchCustomerGroupIds.ToArray(),
            salesEmployeeId: salesEmployeeId,
            email: model.SearchEmail,
            username: model.SearchUsername,
            firstName: model.SearchFirstName,
            lastName: model.SearchLastName,
            company: model.SearchCompany,
            phone: model.SearchPhone,
            zipPostalCode: model.SearchZipPostalCode,
            loadOnlyWithShoppingCart: false);

        try
        {
            var bytes = await _exportManager.Export(customers);
            return File(bytes, "text/xls", "customers.xlsx");
        }
        catch (Exception exc)
        {
            Error(exc);
            return RedirectToAction("List");
        }
    }

    [PermissionAuthorizeAction(PermissionActionName.Export)]
    [HttpPost]
    public async Task<IActionResult> ExportExcelSelected(string selectedIds)
    {
        var customers = new List<Customer>();
        if (selectedIds != null)
        {
            var ids = selectedIds
                .Split([','], StringSplitOptions.RemoveEmptyEntries)
                .Select(x => x)
                .ToArray();
            customers.AddRange(await _customerService.GetCustomersByIds(ids));
        }

        var bytes = await _exportManager.Export(customers);
        return File(bytes, "text/xls", "customers.xlsx");
    }

    #endregion
}